#!/usr/bin/env python
# -*- coding: utf-8 -*-

#
# This program is free software; you can redistribute it and/or modify  
# it under the terms of the GNU General Public License as published by  
# the Free Software Foundation; either version 2 of the License, or     
# (at your option) any later version.                                   
#                                                                         
# A copy of the license can be found in the license.txt file supplied   
# with this software or at: http://www.gnu.org/copyleft/gpl.html       
#

__AUTHOR__  = 'kuerant@gmail.com'
__VERSION__ = '0.1.1'
__LAST_UPDATE__ = '0.1.1@2009-10-09'

#
# TODO : 
# 
#

import  sys, os.path, os, re, socket
import  time
import  traceback
import  tempfile
import  SocketServer

import ConfigParser as configparser


CATEGORY_ROOT = 'root'

LEVEL_NONE = 0
LEVEL_FATAL = 10
LEVEL_ERROR = 20
LEVEL_WARN = 30
LEVEL_INFO = 50
LEVEL_DEBUG = 70
LEVEL_TRACE = 90
LEVEL_ANY = 100

class   Item(object) :
    def __init__(self, timestamp=0, category=CATEGORY_ROOT, level=LEVEL_INFO, filename=None, lineno=None, module=None, function=None, host=None, pid=0, message=None) :
        self.timestamp = timestamp
        self.category = category
        self.level = level
        self.filename = filename
        self.lineno = lineno
        self.module = module
        self.funciton = function
        self.message = message
        self.pid = pid
        self.host = host
        ## __init__()
    ## class Item


#######################################################################

class   Appender(object) : 
    def setLayout(self, layout) :
        pass
        ## setLayout()

    ## class Appender

class   ConsoleAppender(Appender) :
    def __init__(self, layout=None) :
        if not layout :
            self.layout = Layout()
        else :
            self.layout = layout
        ## __init__

    def append(self, item) :
        if item.level < LEVEL_INFO :
            sys.stderr.write(''.join((self.layout.format(item), '\n')))
        else :
            sys.stdout.write(''.join((self.layout.format(item), '\n')))
        ## append()

    ## class ConsoleAppender

class   FileAppender(Appender) :
    ROLLING_NONE   = 0
    ROLLING_SIZE   = 1
    ROLLING_DAILY  = 2
    ROLLING_HOURLY = 3

    pattern_MB = re.compile('^(\d+)\s*MB$', re.IGNORECASE)
    pattern_GB = re.compile('^(\d+)\s*GB$', re.IGNORECASE)

    def __init__(self, filename, layout=None, rolling=None) :
        self.filename = filename
        self.rolling_spec = rolling.strip().lower()
        self.__init_rolling()

        if not layout :
            self.layout = Layout()
        else :
            self.layout = layout

        self._filters = []
        self.logfile = None
        ## __init__

    def __del__(self) :
        if self.logfile :
            self.logfile.close()
            self.logfile = None
        ## __del__()

    def append(self, item) :
        if not self.__passFilter(item) :
            return

        self.__rollFile(item.timestamp)
        if self.logfile : self.logfile.write(''.join((self.layout.format(item), '\n')))
        ## append()

    def __rollFile(self, timestamp) :
        if self.logfile :
            if self.rolling_type == self.ROLLING_SIZE :
                size_current = self.logfile.tell()
                if size_current > self.rolling_limit :
                    self.logfile.close()
                    self.logfile = None
            elif self.rolling_type == self.ROLLING_DAILY :
                if timestamp > self.rolling_limit :
                    self.logfile.close()
                    self.logfile = None
            elif self.rolling_type == self.ROLLING_HOURLY :
                if timestamp > self.rolling_limit :
                    self.logfile.close()
                    self.logfile = None


        if not self.logfile :
            self.__openLogFile(timestamp)
        ## __rollFile()

    def __init_rolling(self) :
        self.rolling_type = self.ROLLING_NONE
        if self.rolling_spec in ('no','none') :
            self.rolling_type = self.ROLLING_NONE
        elif 'daily' == self.rolling_spec :
            self.rolling_type = self.ROLLING_DAILY
        elif 'hourly' == self.rolling_spec :
            self.rolling_type = self.ROLLING_HOURLY
        else :
            matched = self.pattern_MB.match(self.rolling_spec)
            if matched :
                mb = int(matched.group(1)) * 1048576
                self.rolling_limit = mb
                self.rolling_type = self.ROLLING_SIZE

            else :
                matched = self.pattern_GB.match(self.rolling_spec)
                if matched :
                    gb = int(matched.group(1)) * 1048576 * 1024
                    self.rolling_limit = gb
                    self.rolling_type = self.ROLLING_SIZE
                else :
                    raise NotImplementedError
        ## __init_rolling()

    def __openLogFile(self, timestamp) :
        if self.logfile : return

        filename = self.filename
        if self.rolling_type == self.ROLLING_NONE :
            # No rolling
            pass
        elif self.rolling_type == self.ROLLING_SIZE :
            # rolling by size
            pass
        elif self.rolling_type == self.ROLLING_DAILY :
            # rolling by daily
            x = time.localtime(timestamp)
            suffix = '%04d-%02d-%02d' % (x.tm_year, x.tm_mon, x.tm_mday)
            filename = '.'.join((filename, suffix))
            y = time.localtime(timestamp + 86400)
            self.rolling_limit = time.mktime((y.tm_year,y.tm_mon,y.tm_mday,0,0,0,0,0,0))
        elif self.rolling_type == self.ROLLING_HOURLY :
            # rolling by hourly
            x = time.localtime(timestamp)
            suffix = '%04d-%02d-%02d-%02d' % (x.tm_year, x.tm_mon, x.tm_mday, x.tm_hour)
            filename = '.'.join((filename, suffix))
            y = time.localtime(timestamp + 3600)
            self.rolling_limit = time.mktime((y.tm_year,y.tm_mon,y.tm_mday,y.tm_hour,0,0,0,0,0))

        # 1 -- means line-buffer
        self.logfile = file(filename, 'a', 1)
        ## __openLogFile()

    def setLayout(self, layout) :
        self.layout = layout
        ## setLayout()

    def setFilter(self, filter_order, filter) :
        if isinstance(filter_order, str) :
            filter_order = int(filter_order)
        for i in range(len(self._filters), filter_order, 1) :
            self._filters.append( None )

        if len(self._filters) >= filter_order :
            self._filters[i] = filter
        else :
            self._filters.append( filter )
        ## setFilter()

    def __passFilter(self, log_record) :
        if not self._filters :
            # if no filters, passed
            return True

        for filter in self._filters :
            if filter :
                decision = filter.decide( log_record )
                if Filter.ACCEPT == decision :
                    return True
                elif Filter.DENY == decision :
                    return False

        # not accept by any filter, filtered
        return False
        ## __passFilter()

    ## class FileAppender


#######################################################################

class   Layout(object) :
    level_name = {
        LEVEL_NONE:'NONE', 
        LEVEL_FATAL:'FATAL',LEVEL_ERROR:'ERROR',LEVEL_WARN:'WARN',LEVEL_INFO:'INFO',
        LEVEL_DEBUG:'DEBUG',LEVEL_TRACE:'TRACE',
        LEVEL_ANY:'ANY'
    }

    def format(self, item) :
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item.timestamp))
        return '%s %d %s %s:%d [%s] %s' % (timestamp, item.pid, item.category, item.filename, item.lineno, self.getLevelName(item.level), item.message)
        ## format()

    def getLevelName(self, level) :
        if level in self.level_name :
            return self.level_name[level]

        return 'NONE'
        ## getLevelName()
    ## class Layout

class   ConsoleLayout(Layout) :
    '''Layout for Console Appender
    '''
    def format(self, item) :
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item.timestamp))
        return '%s %s [%s] %s' % (timestamp, item.category, self.getLevelName(item.level), item.message)
        ## format()
    ## class ConsoleLayout

class   SimpleLayout(Layout) :
    pass
    ## class SimpleLayout

class   PatternLayout(Layout) :
    pass
    ## class PatternLayout

class   ConversionPatternLayout(PatternLayout) :
    '''
    1  %t -- timestamp
    2  %c -- category
    3  %p -- level/priority
    4  %f -- filename
    5  %l -- lineno
    6  %M -- module
    7  %F -- function
    8  %m -- message
    9  %h -- host
    10 %P -- pid
    '''
    def __init__(self, pattern=None) :
        self._conversion = None
        if pattern :
            self._conversion = pattern.strip()

            self.__buildSpecifierTable()
            self.__parseConversionPattern()
        ## __init__()

    def __buildSpecifierTable(self) :
        self._specifier = { 't':1, 'c':2, 'p':3, 'f':4, 'l':5, 'M':6, 'F':7, 'm':8, 'h':9, 'P':10 }
        ## __buildSpecifierTable()

    def __parseConversionPattern(self) :
        self._pattern = []
        begin_index = 0
        sentinel_index = len(self._conversion)
        i = 0
        while i < sentinel_index :
            if '%' == self._conversion[i] and (i + 1) < sentinel_index :
                spec = self._conversion[ i + 1 ]
                if spec in self._specifier :
                    self._pattern.append( (0, self._conversion[begin_index : i]) )
                    self._pattern.append( (self._specifier[ spec ], None) )

                    begin_index = i + 2
                    i += 1
            i += 1
        if begin_index < sentinel_index :
            self._pattern.append( (0, self._conversion[ begin_index : ]) )
        ## __parseConversionPattern()

    def __fillSpecifier(self, id, item) :
        if 1 == id :
            return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item.timestamp))
        elif 2 == id :
            return item.category
        elif 3 == id :
            return self.getLevelName(item.level)
        elif 4 == id :
            return item.filename
        elif 5 == id :
            return str(item.lineno)
        elif 6 == id :
            return item.module
        elif 7 == id :
            return item.function
        elif 8 == id :
            return item.message
        elif 9 == id :
            if item.host : return item.host
            return 'unknown'
        elif 10 == id :
            return str(item.pid)

        return ''
        ## __fillSpecifier()

    def format(self, item) :
        result = []
        for id,slice in self._pattern :
            if id > 0 :
                result.append(self.__fillSpecifier(id, item))
            else :
                if slice :
                    result.append(slice)

        return ''.join(result)
        ## format()

    ## class ConversionPatternLayout


#######################################################################

class   Filter(object) :
    DENY   = -1
    NEUTRAL = 0
    ACCEPT  = 1

    # The returned value must be one of DENY, NEUTRAL or ACCEPT.
    def decide(self, log_record) :
        raise NotImplementedError, 'Filter::decide() must be override.'
        ## decide()

    ## class Filter


class   LevelMatchFilter(Filter) :
    def __init__(self, level_to_match, accept_on_match) :
        self.level_to_match = level_to_match
        self.accept_on_match = accept_on_match
        ## __init__()

    # The returned value must be one of DENY, NEUTRAL or ACCEPT.
    def decide(self, log_record) :
        if log_record.level in self.level_to_match :
            if self.accept_on_match :
                return Filter.ACCEPT 
            else :
                return Filter.DENY

        return Filter.NEUTRAL
        ## decide()

    ## class LevelMatchFilter


#######################################################################

class   Logger(object) :
    def __init__(self, category=CATEGORY_ROOT, appenders=()) :
        if not category :
            self.category = CATEGORY_ROOT
        else :
            self.category = category

        self.appenders = appenders
        self.logging_level = LEVEL_INFO
        self.hostname = socket.gethostname()
        self.pid = os.getpid()
        ## __init__()

    def error(self, format, *args) :
        filename,lineno,module,function = self.__tracestack()
        item = Item(time.time(),self.category,LEVEL_ERROR,filename,lineno,module,function,self.hostname,self.pid,format % args)
        self.append(item)
        ## error()

    def warn(self, format, *args) :
        filename,lineno,module,function = self.__tracestack()
        item = Item(time.time(),self.category,LEVEL_WARN,filename,lineno,module,function,self.hostname,self.pid,format % args)
        self.append(item)
        ## warn()

    def info(self, format, *args) :
        filename,lineno,module,function = self.__tracestack()
        item = Item(time.time(),self.category,LEVEL_INFO,filename,lineno,module,function,self.hostname,self.pid,format % args)
        self.append(item)
        ## info()

    def debug(self, format, *args) :
        if not self.isDebugEnabled() : return
        filename,lineno,module,function = self.__tracestack()
        item = Item(time.time(),self.category,LEVEL_DEBUG,filename,lineno,module,function,self.hostname,self.pid,format % args)
        self.append(item)
        ## debug()

    def trace(self, format, *args) :
        if not self.isTraceEnabled() : return
        filename,lineno,module,function = self.__tracestack()
        item = Item(time.time(),self.category,LEVEL_TRACE,filename,lineno,module,function,self.hostname,self.pid,format % args)
        self.append(item)
        ## trace()

    def excpt(self) :
        filename,lineno,module,function = self.__tracestack()
        nil, t, v, tbinfo = self.compact_traceback()
        item = Item(time.time(),self.category,LEVEL_FATAL,filename,lineno,module,function,self.hostname,self.pid,'%s:%s %s' % (t, v, tbinfo))
        self.append(item)
        ## excpt()

    def append(self, item) :
        for appender in self.appenders :
            appender.append(item)
        ## append()

    def __tracestack(self) :
        stack = traceback.extract_stack()
        LAYER = -3
        filename = os.path.basename(stack[LAYER][0])
        lineno   = stack[LAYER][1]
        module   = stack[LAYER][2]
        function = stack[LAYER][3]
        return  (filename, lineno, module, function)
        ## __tracestack()

    def compact_traceback(self):
        ''' 
        nil, t, v, tbinfo = compact_traceback()
        log.error('trackback exception : (%s:%s %s)', t, v, tbinfo)
        '''
        t, v, tb = sys.exc_info()
        tbinfo = []
        assert tb # Must have a traceback
        while tb: 
            tbinfo.append((
                tb.tb_frame.f_code.co_filename,
                tb.tb_frame.f_code.co_name,
                str(tb.tb_lineno)
                ))  
            tb = tb.tb_next

        # just to be safe
        del tb

        file, function, line = tbinfo[-1]
        info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
        return (file, function, line), t, v, info
        ## compact_traceback()

    def setLevel(self, level) :
        self.logging_level = level
        ## setLevel()

    def isDebugEnabled(self) :
        return LEVEL_DEBUG <= self.logging_level
        ## isDebugEnabled()

    def isTraceEnabled(self) :
        return LEVEL_TRACE <= self.logging_level
        ## isTraceEnabled()

    ## class Logger

#######################################################################

class   Manager(object) :
    SECTION_NAME = 'logging'

    categories = {}
    appenders = {}
    
    default_appender = None

    @classmethod
    def getLogger(cls, category) :
        if not category :
            category = CATEGORY_ROOT
        if category in cls.categories :
            return cls.categories[ category ]

        if CATEGORY_ROOT not in cls.categories :
            appender = ConsoleAppender(layout=ConsoleLayout())
            logger = Logger(category=CATEGORY_ROOT, appenders=(appender,))
            cls.categories[ CATEGORY_ROOT ] = logger

        if category != CATEGORY_ROOT :
            # find parent appender
            # if not found, use RootLogger
            if not cls.default_appender :
                cls.default_appender = ConsoleAppender(layout=Layout())
            #logger = Logger(category=category, appenders=(cls.default_appender,))
            root_logger = cls.categories[CATEGORY_ROOT]
            appender_list = root_logger.appenders
            logging_level = root_logger.logging_level

            logger = Logger(category=category, appenders=appender_list)
            logger.setLevel(logging_level)

            cls.categories[ category ] = logger

        return cls.categories[ category ]
        ## getLogger()


    @classmethod
    def configure(cls, configuration) :
        '''parse configuration 
        '''
        fd,fn = tempfile.mkstemp(suffix='.ini', prefix='logging-')
        os.close(fd)

        fp = file(fn, 'w')
        fp.write("[%s]\n" % cls.SECTION_NAME)
        fp.write(configuration)
        fp.write("\n")
        fp.close()

        cls.loadConfigure(fn)

        os.remove(fn)
        ## configure()

    @classmethod
    def loadConfigure(cls, filename) :
        '''load configuration from file
        '''
        cls.categories = {}
        cls.appenders = {}

        ini = configparser.ConfigParser()
        ini.optionxform = lambda x: x
        ini.read( filename )
        if not ini.has_section(cls.SECTION_NAME) :
            raise RuntimeError, 'Section [%s] does NOT exist.' % cls.SECTION_NAME
        items = ini.items(cls.SECTION_NAME, raw=True)

        # construct appenders
        pattern_appender = re.compile('^logging.appender.([^\.]*)$', re.IGNORECASE)
        for option,value in items :
            matched = pattern_appender.match(option)
            if not matched : continue
            appender_name = matched.group(1)
            appender_class = value
            if 'ConsoleAppender' == appender_class : 
                layout = ConsoleLayout()
                appender = ConsoleAppender(layout)
                cls.appenders[ appender_name ] = appender
            elif 'FileAppender' == appender_class :
                layout = Layout()

                # logging.appender.A.file = /path/to/log-file-name
                option_file = '.'.join((option, 'file'))
                if not ini.has_option(cls.SECTION_NAME, option_file) : 
                    raise RuntimeError,'filename for FileAppender "%s" is required.' % appender_name
                appender_file = ini.get(cls.SECTION_NAME, option_file )

                # logging.appender.A.rolling = none|daily|hourly|quarterly|100MB|1GB
                option_rolling = '.'.join((option, 'rolling'))
                if ini.has_option(cls.SECTION_NAME, option_rolling) : 
                    file_rolling = ini.get(cls.SECTION_NAME, option_rolling)
                else :
                    file_rolling = 'none'

                appender = FileAppender(appender_file, layout, file_rolling)
                cls.appenders[ appender_name ] = appender
            else :
                raise RuntimeError,'Appender type "%s" is NOT supported.' % appender_class

        # parse layout of appender
        pattern_appender_layout = re.compile('^logging.appender.([^\.]*)\.layout$', re.IGNORECASE)
        pattern_appender_conversion_pattern = re.compile('^logging.appender.([^\.]*)\.layout\.conversionpattern$', re.IGNORECASE)
        for option,value in items :
            matched = pattern_appender_layout.match(option)
            if not matched : continue
            appender_name = matched.group(1)
            if appender_name not in cls.appenders : continue
            appender = cls.appenders[ appender_name ]
            appender_layout_class = value
            if 'PatternLayout' == appender_layout_class :
                option_conversion_pattern = '.'.join(('logging','appender',appender_name,'layout','ConversionPattern'))
                if ini.has_option(cls.SECTION_NAME, option_conversion_pattern):
                    _pattern = ini.get(cls.SECTION_NAME, option_conversion_pattern)
                else : 
                    _pattern = None
                _layout = ConversionPatternLayout(_pattern)
                appender.setLayout( _layout )
            elif 'SimpleLayout' == appender_layout_class :
                _layout = SimpleLayout()
                appender.setLayout( _layout )
            else :
                raise RuntimeError,'Appender Layout "%s" is NOT supported.' % appender_layout_class

        logging_levels = {
            'NONE':LEVEL_NONE,
            'FATAL':LEVEL_FATAL,'ERROR':LEVEL_ERROR,'INFO':LEVEL_INFO,'WARN':LEVEL_WARN,
            'DEBUG':LEVEL_DEBUG,'TRACE':LEVEL_TRACE,
            'ANY':LEVEL_ANY, 'ALL':LEVEL_ANY,
        }

        # parse filter of appender
        pattern_appender_filter = re.compile('^logging.appender.([^\.]*)\.filter\.(\d+)$', re.IGNORECASE)
        pattern_appender_filter_leveltomatch = re.compile('^logging.appender.([^\.]*)\.filter\.(\d+)\.leveltomatch$', re.IGNORECASE)
        pattern_appender_filter_stringtomatch = re.compile('^logging.appender.([^\.]*)\.filter\.(\d+)\.stringtomatch$', re.IGNORECASE)
        pattern_appender_filter_acceptonmatch = re.compile('^logging.appender.([^\.]*)\.filter\.(\d+)\.acceptonmatch$', re.IGNORECASE)
        for option,value in items :
            matched = pattern_appender_filter.match(option)
            if not matched : continue
            appender_name = matched.group(1)
            if appender_name not in cls.appenders : continue
            appender = cls.appenders[ appender_name ]
            appender_filter_level = matched.group(2)
            appender_filter_class = value
            if 'LevelMatchFilter' == appender_filter_class :
                option_level_to_match = '.'.join(('logging','appender',appender_name,'filter',appender_filter_level,'LevelToMatch'))
                if ini.has_option(cls.SECTION_NAME, option_level_to_match) :
                    option_accept_on_match = '.'.join(('logging','appender',appender_name,'filter',appender_filter_level,'AcceptOnMatch'))
                    accept_on_match = True
                    if ini.has_option(cls.SECTION_NAME, option_accept_on_match) :
                        _accept_on_match = ini.get(cls.SECTION_NAME, option_accept_on_match)
                        if 'true' == _accept_on_match.lower() :
                            accept_on_match = True
                        else :
                            accept_on_match = False
                    level_to_match = ini.get(cls.SECTION_NAME, option_level_to_match)
                    levels_str = level_to_match.split(',')
                    level_to_match = [logging_levels[x.strip()] for x in levels_str]
                    _filter = LevelMatchFilter( level_to_match, accept_on_match )
                    appender.setFilter( appender_filter_level, _filter )
                else :
                    raise RuntimeError
            elif 'LevelRangeFilter' == appender_filter_class :
                raise RuntimeError,'Appender Layout "%s" is NOT supported.' % appender_filter_class
            elif 'StringMatchFilter' == appender_filter_class :
                raise RuntimeError,'Appender Layout "%s" is NOT supported.' % appender_filter_class
            else :
                raise RuntimeError,'Appender Layout "%s" is NOT supported.' % appender_filter_class

        # parse logging Threshold
        logging_threshold = LEVEL_ANY
        for option,value in items :
            if option.lower() == 'logging.threshold' :
                # logging.threshold = ALL | DEBUG | INFO | WARN | ERROR | FATAL | NONE
                threshold = value.upper()
                if threshold not in logging_levels :
                    raise RuntimeError,'Logging threshold "%s" is NOT supported.' % value
                logging_threshold = logging_levels[ threshold ]

        # construct logger for categories
        def parseCategories(value) :
            appender_list = []

            appenders = [ x.strip() for x in value.split(',') ]
            level = appenders[ 0 ].upper()
            if level in logging_levels :
                logging_level = logging_levels[ level ]
                appenders = appenders[1:]
            else :
                logging_level = LEVEL_ANY

            appender_list = []
            for appender in appenders :
                if appender not in cls.appenders :
                    raise RuntimeError,'Appender "%s" is NOT configured.' % appender
                appender_list.append( cls.appenders[ appender ] )

            return logging_level, appender_list
            ## parseCategories()

        pattern_rootlogger = re.compile('^logging.rootLogger$', re.IGNORECASE)
        pattern_logger = re.compile('^logging.logger.([^\.]*)$', re.IGNORECASE)
        for option,value in items :
            matched = pattern_rootlogger.match(option) 
            if matched :
                # logging.rootLogger = [LEVEL,] A1[, A2 [,...]]
                logging_level, appender_list = parseCategories( value )
                root_logger = Logger(category=CATEGORY_ROOT, appenders=appender_list)
                if logging_level > logging_threshold :
                    logging_level = logging_threshold
                root_logger.setLevel( logging_level )
                cls.categories[ CATEGORY_ROOT ] = root_logger

                continue
                # if matched (rootLogger)
            matched = pattern_logger.match(option) 
            if matched :
                # logging.logger.CATEGORY = [LEVEL,] A1[, A2 [,...]]
                logging_level, appender_list = parseCategories( value )

                category_name = matched.group(1)
                logger = Logger(category=category_name, appenders=appender_list)
                if logging_level > logging_threshold :
                    logging_level = logging_threshold
                logger.setLevel( logging_level )
                cls.categories[ category_name ] = logger

                continue
                # if matched (logger.CATEGORY)

        ## loadConfigure()

    ## class Manager

#######################################################################

def getLogger(category=None) :
    return Manager.getLogger(category)
    ## getLogger()

def configure(configuration) :
    '''parse configuration 
    '''
    Manager.configure( configuration )
    ## configure()

def loadConfigure(filename) :
    '''load configuration from file
    '''
    Manager.loadConfigure( filename )
    ## loadConfigure()


#######################################################################

class UdpLoggingHandler(SocketServer.DatagramRequestHandler) :
    def handle(self) :
        data = self.rfile.read()
        item = self.parse( data )
        if item :
            logger = getLogger(item.category)
            logger.append(item)
        ## handle()

    def parse(self, data) :
        item = Item()
        field_count = 0
        fields = data.split("\t")
        for f in fields :
            try :
                k,v = f.split(':', 1)
            except :
                continue

            if 'timestamp' == k :
                item.timestamp = float(v)
            elif 'category' == k :
                item.category = v
            elif 'level' == k :
                item.level = int(v)
            elif 'filename' == k :
                item.filename = v
            elif 'lineno' == k :
                item.lineno = int(v)
            elif 'module' == k :
                item.module = v
            elif 'funciton' == k :
                item.function = v
            elif 'message' == k :
                item.message = v
            elif 'pid' == k :
                item.pid = int(v)
            elif 'host' == k :
                item.host = v

            field_count += 1

        if field_count < 10 :
            # incomplete record, discard it
            return None

        return item
        ## parse()

    ## class UdpLoggingHandler


def main() :

    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("", "--config", dest="config", type="string",
                        help='path to logging configure file')
    parser.add_option("", "--udpport", dest="udpport", type="int", default=4344,
                        help='UDP port number of logging-server to listen')

    parser.add_option("", "--daemon", dest="daemon", action="store_true", default=False,
                        help='run as logging daemon')

    (options, args) = parser.parse_args()
    if not options.udpport or not options.config:
        parser.print_help( sys.stdout )
        sys.exit(0)

    # load configure 
    loadConfigure(options.config)

    server = SocketServer.UDPServer(('',options.udpport), UdpLoggingHandler)
    server.serve_forever()

    ## main()

if __name__ == '__main__' :
    main()

